package org.hentai.callback;

import com.aizuda.zlm4j.callback.*;
import com.aizuda.zlm4j.structure.*;
import com.sun.jna.CallbackThreadInitializer;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hentai.config.MediaServerConfig;
import org.hentai.context.MediaServerContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import static org.hentai.context.MediaServerContext.ZLM_API;

@Slf4j
public class LiveCallback {

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class LiveStream {
        String schema;

        String host;

        String app;

        String stream;

        int port;

        String param;

        String httpView;
    }

    /**
     * 媒体资源回调接口
     *
     **/
    public interface IMKSourceHandleCallBack {

        void invoke(MK_MEDIA_SOURCE ctx);
    }

    public static LiveStream livingMessage(MK_MEDIA_INFO url_info) {
        String app = ZLM_API.mk_media_info_get_app(url_info);
        String stream = ZLM_API.mk_media_info_get_stream(url_info);
        String schema = ZLM_API.mk_media_info_get_schema(url_info);
        String host = ZLM_API.mk_media_info_get_host(url_info);
        int port = ZLM_API.mk_media_info_get_port(url_info);
        //这里拿到访问路径后(例如rtmp://xxxx/xxx/xxx?token=xxxx其中?后面就是拿到的参数)的参数
        String param = ZLM_API.mk_media_info_get_params(url_info);

        return new LiveStream(schema, host, app, stream, port, param, String.format("http://127.0.0.1:7080/%s/%s.live.flv", app, stream));
    }

    /**
     * 停止rtsp/rtmp/http-flv会话后流量汇报事件广播
     */
    @Component
    public static class MKFlowReportCallBack implements IMKFlowReportCallBack {
        /**
         * 停止rtsp/rtmp/http-flv会话后流量汇报事件广播
         *
         * @param url_info      播放url相关信息
         * @param total_bytes   耗费上下行总流量，单位字节数
         * @param total_seconds 本次tcp会话时长，单位秒
         * @param is_player     客户端是否为播放器
         */
        public void invoke(MK_MEDIA_INFO url_info, long total_bytes, long total_seconds, int is_player, MK_SOCK_INFO sender) {

        }
    }
    /**
     * 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限
     */
    @Component
    public static class MKHttpAccessCallBack implements IMKHttpAccessCallBack {
        /**
         * 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限
         * @param parser http请求内容对象
         * @param path 文件绝对路径
         * @param is_dir path是否为文件夹
         * @param invoker 执行invoker返回本次访问文件的结果
         * @param sender http客户端相关信息
         */
        @Override
        public void invoke(MK_PARSER parser, String path, int is_dir, MK_HTTP_ACCESS_PATH_INVOKER invoker, MK_SOCK_INFO sender){

        }
    }

    /**
     * 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射
     */
    @Component
    public static class MKHttpBeforeAccessCallBack implements IMKHttpBeforeAccessCallBack {
        /**
         * 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射
         * 在该事件中通过自行覆盖path参数，可以做到譬如根据虚拟主机或者app选择不同http根目录的目的
         *
         * @param parser http请求内容对象
         * @param path   文件绝对路径,覆盖之可以重定向到其他文件
         * @param sender http客户端相关信息
         */
        public void invoke(MK_PARSER parser, String path, MK_SOCK_INFO sender){

        }
    }

    /**
     * 收到http api请求广播(包括GET/POST)
     */
    @Component
    public static class MKHttpRequestCallBack implements IMKHttpRequestCallBack {
        /**
         * 收到http api请求广播(包括GET/POST)
         *
         * @param parser   http请求内容对象
         * @param invoker  执行该invoker返回http回复
         * @param consumed 置1则说明我们要处理该事件
         * @param sender   http客户端相关信息
         */
        @Override
        public void invoke(MK_PARSER parser, MK_HTTP_RESPONSE_INVOKER invoker, IntByReference consumed, MK_SOCK_INFO sender) {
            consumed.setValue(0);
        }
    }

    /**
     * 日志输出广播
     */
    @Slf4j
    @Component
    public static class MKLogCallBack implements IMKLogCallBack {

        public MKLogCallBack() {
            //回调使用同一个线程
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "MediaServerLogThread"));
        }

        /**
         * 日志输出广播
         *
         * @param level    日志级别
         * @param file     源文件名
         * @param line     源文件行
         * @param function 源文件函数名
         * @param message  日志内容
         */
        public void invoke(int level, String file, int line, String function, String message) {
            switch (level) {
                case 0:
                    log.trace("【MediaServer】{}", message);
                    break;
                case 1:
                    log.debug("【MediaServer】{}", message);
                    break;
                case 2:
                    log.info("【MediaServer】{}", message);
                    break;
                case 3:
                    log.warn("【MediaServer】{}", message);
                    break;
                case 4:
                    log.error("【MediaServer】{}", message);
                    break;
                default:
                    log.info("【MediaServer】{}", message);
                    break;
            }
        }
    }

    /**
     * 未找到流后会广播该事件，请在监听该事件后去拉流或其他方式产生流，这样就能按需拉流了
     *
     * @author lidaofu
     * @since 2023/11/23
     **/
    @Component
    public static class MKNoFoundCallBack implements IMKNoFoundCallBack {

        public MKNoFoundCallBack() {
            //回调使用同一个线程
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "MediaNoFoundThread"));
        }
        /**
         * 未找到流后会广播该事件，请在监听该事件后去拉流或其他方式产生流，这样就能按需拉流了
         *
         * @param url_info 播放url相关信息
         * @param sender   播放客户端相关信息
         * @return 1 直接关闭
         * 0 等待流注册
         */
        public int invoke(MK_MEDIA_INFO url_info, MK_SOCK_INFO sender){
            //这里可以实现按需拉流，这里面新起个线程去操作拉起流
            return 1;
        }
    }

    /**
     * 无人观看回调
     *
     * @author lidaofu
     * @since 2023/11/23
     **/
    @Component
    public static class MKNoReaderCallBack implements IMKNoReaderCallBack {
        public MKNoReaderCallBack() {
            //回调使用同一个线程
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "MediaNoReaderThread"));
        }

        /**
         * 某个流无人消费时触发，目的为了实现无人观看时主动断开拉流等业务逻辑
         *
         * @param sender 该MediaSource对象
         */
        public void invoke(MK_MEDIA_SOURCE sender) {
            //无人观看时候可以调用下面的实现关流 不调用就代表不关流 需要配置protocol.auto_close 为 0 这里才会有回调
            //MediaServerContext.ZLM_API.mk_media_source_close(sender,0);
        }
    }

    /**
     * 播放rtsp/rtmp/http-flv/hls事件广播，通过该事件控制播放鉴权
     *
     * @author lidaofu
     * @since 2023/11/23
     **/
    @Component
    public static class MKPlayCallBack implements IMKPlayCallBack {
        public MKPlayCallBack() {
            //回调使用同一个线程
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "MediaPlayThread"));
        }

        /**
         * 播放rtsp/rtmp/http-flv/hls事件广播，通过该事件控制播放鉴权
         *
         * @param url_info 播放url相关信息
         * @param invoker  执行invoker返回鉴权结果
         * @param sender   播放客户端相关信息
         * @see mk_auth_invoker_do
         */
        public void invoke(MK_MEDIA_INFO url_info, MK_AUTH_INVOKER invoker, MK_SOCK_INFO sender) {
            //这里拿到访问路径后(例如http://xxxx/xxx/xxx.live.flv?token=xxxx其中?后面就是拿到的参数)的参数
            // err_msg返回 空字符串表示鉴权成功 否则鉴权失败提示
            //String param = ZLM_API.mk_media_info_get_params(url_info);
            log.info("url_info | {}", url_info);
            ZLM_API.mk_auth_invoker_do(invoker, "");
        }
    }

    /**
     * MediaSource.close()回调事件
     */
    @Component
    public static class MKProxyPlayCloseCallBack implements IMKProxyPlayerCallBack {
        /**
         * MediaSource.close()回调事件
         * 在选择关闭一个关联的MediaSource时，将会最终触发到该回调
         * 你应该通过该事件调用mk_proxy_player_release函数并且释放其他资源
         * 如果你不调用mk_proxy_player_release函数，那么MediaSource.close()操作将无效
         *
         * @param pUser 用户数据指针，通过mk_proxy_player_set_on_close函数设置
         */
        public void invoke(Pointer pUser, int err, String what, int sys_err) {

        }
    }


    /**
     * 推流回调
     *
     * @author lidaofu
     * @since 2023/11/29
     **/
    @Component
    public static class MKPublishCallBack implements IMKPublishCallBack {
        @Autowired
        private MediaServerConfig config;

        public MKPublishCallBack() {
            //回调使用同一个线程
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "MediaPublishThread"));
        }

        /**
         * 收到rtsp/rtmp推流事件广播，通过该事件控制推流鉴权
         *
         * @param url_info 推流url相关信息
         * @param invoker  执行invoker返回鉴权结果
         * @param sender   该tcp客户端相关信息
         * @see mk_publish_auth_invoker_do
         */
        @Override
        public void invoke(MK_MEDIA_INFO url_info, MK_PUBLISH_AUTH_INVOKER invoker, MK_SOCK_INFO sender) {
            LiveStream liveStream = livingMessage(url_info);
            log.info("live info {}", liveStream);
            //这里拿到访问路径后(例如rtmp://xxxx/xxx/xxx?token=xxxx其中?后面就是拿到的参数)的参数
            // err_msg返回 空字符串表示鉴权成功 否则鉴权失败提示
            //MediaServerContext.ZLM_API.mk_publish_auth_invoker_do(invoker,"",1,0);
            MK_INI option = ZLM_API.mk_ini_create();
            ZLM_API.mk_ini_set_option_int(option, "enable_mp4", config.getEnable_mp4());
            ZLM_API.mk_ini_set_option_int(option, "enable_audio", config.getEnable_audio());
            ZLM_API.mk_ini_set_option_int(option, "enable_fmp4",config.getEnable_fmp4());
            ZLM_API.mk_ini_set_option_int(option, "enable_hls_fmp4",config.getEnable_hls_fmp4());
            ZLM_API.mk_ini_set_option_int(option, "enable_ts", config.getEnable_ts());
            ZLM_API.mk_ini_set_option_int(option, "enable_hls",config.getEnable_hls());
            ZLM_API.mk_ini_set_option_int(option, "enable_rtsp", config.getEnable_rtsp());
            ZLM_API.mk_ini_set_option_int(option, "enable_rtmp", config.getEnable_rtmp());
            ZLM_API.mk_ini_set_option_int(option, "auto_close", config.getAuto_close());
            ZLM_API.mk_ini_set_option_int(option, "mp4_max_second", config.getMp4_max_second());
            ZLM_API.mk_ini_set_option_int(option, "segNum", config.getSegNum());
            //流名称替换
            //ZLM_API.mk_ini_set_option(option, "stream_replace", "test1");
            ZLM_API.mk_publish_auth_invoker_do2(invoker, "", option);
            ZLM_API.mk_ini_release(option);
        }
    }

    /**
     * 录制mp4分片文件成功后广播
     */
    @Component
    public static class MKRecordMp4CallBack implements IMKRecordMp4CallBack {
        public MKRecordMp4CallBack() {
            //回调使用同一个线程
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "RecordMp4Thread"));
        }

        /**
         * 录制mp4分片文件成功后广播
         */
        public void invoke(MK_RECORD_INFO info) {
            //录制mp4成功回调 通过mk_record_info_get_*获取各种信息
            String path = MediaServerContext.ZLM_API.mk_record_info_get_file_path(info);
            String folder = MediaServerContext.ZLM_API.mk_record_info_get_folder(info);
            float timeLen = MediaServerContext.ZLM_API.mk_record_info_get_time_len(info);
            long startTime = MediaServerContext.ZLM_API.mk_record_info_get_start_time(info);
        }
    }

    /**
     * 录制ts分片文件成功后广播
     */
    @Component
    public static class MKRecordTsCallBack implements IMKRecordTsCallBack {
        public MKRecordTsCallBack() {
            //回调使用同一个线程
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "RecordTsThread"));
        }

        /**
         * 录制ts分片文件成功后广播
         */
        public void invoke(MK_RECORD_INFO info) {
            //录制ts成功回调 通过mk_ts_info_get_*获取各种信息
            String path = MediaServerContext.ZLM_API.mk_record_info_get_file_path(info);
            String folder = MediaServerContext.ZLM_API.mk_record_info_get_folder(info);
            float timeLen = MediaServerContext.ZLM_API.mk_record_info_get_time_len(info);
            long startTime = MediaServerContext.ZLM_API.mk_record_info_get_start_time(info);
        }
    }

    /**
     * 寻找流回调
     */
    public static class MKSourceFindCallBack implements IMKSourceFindCallBack {
        private IMKSourceHandleCallBack imkSourceHandleCallBack;

        public MKSourceFindCallBack(IMKSourceHandleCallBack imkSourceHandleCallBack) {
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "MediaSourceFindThread"));
            this.imkSourceHandleCallBack=imkSourceHandleCallBack;
        }

        public void invoke(Pointer user_data, MK_MEDIA_SOURCE ctx) {
            imkSourceHandleCallBack.invoke(ctx);
        }
    }

    /**
     * 注册或反注册MediaSource事件广播
     *
     * @author lidaofu
     * @since 2023/11/23
     **/
    @Component
    @Slf4j
    public static class MKStreamChangeCallBack implements IMKStreamChangeCallBack {

        public MKStreamChangeCallBack() {
            Native.setCallbackThreadInitializer(this, new CallbackThreadInitializer(true, false, "MediaStreamChangeThread"));
        }

        /**
         * 注册或反注册MediaSource事件广播
         *
         * @param regist 注册为1，注销为0
         * @param sender 该MediaSource对象
         */
        public void invoke(int regist, MK_MEDIA_SOURCE sender) {
            //这里进行流状态处理
            String stream = MediaServerContext.ZLM_API.mk_media_source_get_stream(sender);
            String app = MediaServerContext.ZLM_API.mk_media_source_get_app(sender);
            String schema = MediaServerContext.ZLM_API.mk_media_source_get_schema(sender);
            //如果是regist是注销情况下无法获取流详细信息如观看人数等
            log.info("【MediaServer】APP:{} 流:{} 协议：{} {}", app, stream, schema, regist == 1 ? "注册" : "注销");
        }
    }

}
